#!/usr/bin/env python

# copyright 2010 Studio Cypher, LLC
# developer: Will Emigh
# projects@studiocypher.com


import random
import hashlib
import math

from datetime import datetime
from datetime import timedelta


from google.appengine.ext import db
from google.appengine.api import users

class Location(db.Model):
    x = db.IntegerProperty()
    y = db.IntegerProperty()

class ResourcePackage(db.Model):
    resourceOne = db.IntegerProperty()
    resourceTwo = db.IntegerProperty()
    resourceThree = db.IntegerProperty()
    resourceFour = db.IntegerProperty()

    @staticmethod
    def create(one, two, three, four):
        rp = ResourcePackage(resourceOne = one, resourceTwo = two,
                             resourceThree = three, resourceFour = four)
        rp.put()
        return rp

    
class Planet(db.Model):
    # a list of Colony keys
    colonies = db.ListProperty(db.Key)
    type = db.StringProperty()
    resourceStorage = db.ReferenceProperty(ResourcePackage)
    pollution = db.IntegerProperty()
    native_population_capacity = db.IntegerProperty()
    current_population = db.IntegerProperty(default=0)
    goods = db.StringListProperty()
    location = db.ReferenceProperty(Location)
    name = db.StringProperty()
    busy = db.BooleanProperty(default = False)
    solar_system = db.StringProperty()
    imgSrc = db.StringProperty()
	
	    
    def get_distance(self, dest):

        dist_x = self.location.x - dest.location.x
        dist_y = self.location.y - dest.location.y
        dist = (dist_x*dist_x) + (dist_y*dist_y)
        dist = math.sqrt(dist)
        if (self.name == dest.name):
            dist = 0 #uniform distance for testing purposes
        else:
            dist = 5
        
        return int(dist)
    
    # Creates a planet with defaults
    # Sets the location for this planet (used to find "nearby" planets)
    # Give the planet a name
    @staticmethod
    def create(name, imgSrc, rsrcStorage, pop_cap):
        planet = Planet(goods = [Good.get_random_good()])
        location = Location(x=random.randint(1,50), y=random.randint(1,50))
        location.put()
        planet.location = location
        planet.name = name
        planet.imgSrc = imgSrc
        planet.resourceStorage = rsrcStorage
        planet.pollution = 0
        planet.native_population_capacity = pop_cap
        planet.put()
        return planet

    # algorithm to adjust for pollution for water
    # Water --> Extraction amount after pollution = 
    # ( (Extraction amount that you would normally get)  * (pi/2 - ( arctan (pollution - 20 )) /2 ) / pi        
    #
    def waterAdjustForPollution(self, share):
        extrctAmnt = (share * ( (3.14/2) - (math.atan(self.pollution - 20)/2)) )/3.14
        return int(extrctAmnt)

    # algorithm to adjust for pollution for water
    # Organic --> Extraction amount after pollution = 
    # ( (Extraction amount that you would normally get)  * (pi/2 - ( arctan (pollution - 20 )) /2 ) / pi        
    #
    def organicAdjustForPollution(self, share):
        extrctAmnt = (share * ((3.14/2) - (math.atan(self.pollution - 10))))/3.14
        return int(extrctAmnt)



		
    def colonize(self, account):
        outposts = OutPost.all()
        resourcePackage = ResourcePackage.create(0, 0, 0, 0)
        colony = Colony(owner=account, location=self.key(), resourceStorage=resourcePackage)
        colony.put()
        self.colonies.append(colony.key())
        return colony
    
    # Return a string version of the planet
    def to_str(self):
        owner = str(self.owner.nickname)
        string = self.name + " - "+self.goods_str()
        string += " - owned by "+owner
        return string

	# Return a string of the planet icon to use based off of the planet id
    def planets_icon(self):
        query = Planet.all()
        index = 1
        for planet in query:
            if self.location == planet.location:
                break
            index += 1
        iconNumber = index % 9
        return "P"+str(iconNumber)+".png"
    
    # Return a string version of all of the goods
    def goods_str(self):
        return ', '.join(self.goods)
    
    # Return the name of the icon for this planet's primary good
    def goods_icon(self):
        return Good.icon_for(self.goods[0])
    
    # Return up to 20 planets "near" the given location
    # If an account is given, remove any planets owned by the given account
    @staticmethod
    def nearby_planets(location, universe, account = None):
        planets = []
        
        # Get up to 4 planets after this one (but including this one)
        # Why include this one?
        # If a user has lost their only planet, their location will still
        #  be tied to that planet. Showing this planet allows them to
        #  re-take it
        query = Planet.all()
        query.filter("location >= ", location)
        query.filter("universe = ", universe)
        query.order("location")
        results = query.fetch(4)
        for planet in results:
            if account == None or planet.owner.key() != account.key():
                planets.append(planet)
        
        # Get up to 4 planets before this one
        query = Planet.all()
        query.filter("location < ", location)
        query.filter("universe = ", universe)
        query.order("location")
        results = query.fetch(4)
        for planet in results:
            if account == None or planet.owner.key() != account.key():
                planets.append(planet)
        
        return planets
            
    # Assign this planet to the given owner
    # If it's different from the current owner, it also removes
    #  all units from this planet
    def assign_to(self, account):
        if (account == self.owner):
            return
            
        for unit in self.unit_set:
            unit.x_pos = None
            unit.y_pos = None
            unit.planet = None
            unit.put()
            
        self.owner = account
        self.put()

class Count():
    i=0

class Account(db.Model):
    user = db.UserProperty()
    colonies = db.ListProperty(db.Key)
    image = db.BlobProperty()
    about = db.TextProperty()
    email = db.StringProperty()
    created_at = db.DateTimeProperty(auto_now_add=True)
    last_action = db.DateTimeProperty(auto_now_add=True)
    status_message = db.StringProperty()
    xp = db.IntegerProperty(default = 0)
    level = db.IntegerProperty(default = 1)
    nickname = db.StringProperty(default = "Space Chicken")
    solar_system = db.StringProperty()
    current_planet = db.ReferenceProperty(Planet)
	
    
    # Create a new account, include a starting planet, etc.
    @staticmethod
    def create(user, email):
        # TODO - if this user already has an account, just return their account
        # This should be handled by the authentication code, but just to be safe...
        
        # Create the account
        account = Account(user = user, email = user.email())
        account.put()
        q = Planet.all()
        if (q.count() == 0):
        # Create a planet for them and all other planets
            pop = 1000
            planet=Planet.create('Ice Planet', "/assets/images/ice_planet.png", ResourcePackage.create(3, 5, 2, 4).key(), pop)
            pop = 1000
            resourcePackage = ResourcePackage.create(2, 4, 3, 5)
            Planet.create('Paradise', "/assets/images/paradise_planet.png", resourcePackage.key(), pop)
            pop = 1000
            resourcePackage = ResourcePackage.create(4, 2, 5, 3)
            Planet.create('Gas Planet', "/assets/images/gas_planet.png", resourcePackage.key(), pop)
            pop = 1000
            resourcePackage = ResourcePackage.create(5, 3, 4, 2)
            Planet.create('Rocky Planet', "/assets/images/rocky_planet.png", resourcePackage.key(), pop)
        
            # this is our default home world, colonize it
            account.colonies.append(planet.key())
            planet.colonize(account)
            planet.put()
            Count.i+=1
            
            
            
            # create ships
            resourcePackage = ResourcePackage.create(1, 1, 1, 1)  #recycler cost changed to not break the game
            unittype = UnitType.create('RecyclingS', resourcePackage, -1, 'description here')
            resourcePackage = ResourcePackage.create(2, 7, 2, 2)
            unittype = UnitType.create('ColonyS', resourcePackage, -1, 'description here')
            resourcePackage = ResourcePackage.create(2, 7, 2, 2)
            unittype = UnitType.create('FreighterS', resourcePackage, -1, 'description here')
            resourcePackage = ResourcePackage.create(3000, 3000, 2000, 2000)
            unittype = UnitType.create('MegaFreighterS', resourcePackage, -1, 'description here')
            resourcePackage = ResourcePackage.create(2000, 2000, 4000, 2000)
            unittype = UnitType.create('MOPPS', resourcePackage, -1, 'description here')

            # create buildings
            resourcePackage = ResourcePackage.create(2, 2, 2, 2)
            buildingtype = BuildingType.create('Starport', 'building', resourcePackage, -1, 'description here')
            resourcePackage = ResourcePackage.create(300, 300, 1000, 500)
            buildingtype = BuildingType.create('Advanced Starport', 'building', resourcePackage, 0, 'description here')
            resourcePackage = ResourcePackage.create(1, 1, 1, 1)
            buildingtype = BuildingType.create('Small Housing Unit', 'building', resourcePackage, 1, 'description here')
            resourcePackage = ResourcePackage.create(5, 5, 5, 5)
            buildingtype = BuildingType.create('Medium Housing Unit', 'building', resourcePackage, 5, 'description here')
            resourcePackage = ResourcePackage.create(10, 10, 10, 10)
            buildingtype = BuildingType.create('Large Housing Unit', 'building', resourcePackage, 10, 'description here')
            resourcePackage = ResourcePackage.create(2, 2, 2, 2)
            buildingtype = BuildingType.create('Factory', 'building', resourcePackage, -1, 'description here')
            resourcePackage = ResourcePackage.create(8, 3, 3, 3)
            buildingtype = BuildingType.create('Automated Production Lines', 'building', resourcePackage, -5, 'description here')
            resourcePackage = ResourcePackage.create(18, 8, 8, 8)
            buildingtype = BuildingType.create('Nanorobotics Assembly', 'building', resourcePackage, -12, 'description here')
            resourcePackage = ResourcePackage.create(3, 3, 3, 8)
            buildingtype = BuildingType.create('Supercooled Magnetic Extractor Upgrade', 'building', resourcePackage, -5, 'description here')
            resourcePackage = ResourcePackage.create(8, 8, 8, 18)
            buildingtype = BuildingType.create('Deep Core Mining Extractor Upgrade', 'building', resourcePackage, -12, 'description here')
            resourcePackage = ResourcePackage.create(5, 5, 5, 5)
            buildingtype = BuildingType.create('Advanced Factory', 'building', resourcePackage, -5, 'description here')

        else:
            planet = q[Count.i%4]
            account.colonies.append(planet.key())
            planet.colonize(account)
            planet.put()
            Count.i+=1
        
            
        # Their starting location is the location of their planet
        account.current_planet = planet
        account.image = db.Blob("/assets/images/brenden.jpg")
        # Assign them to a universe
        account.solar_system = SolarSystem.random()
        account.put()
        
        return account
        
    def get_offers(self):
        all_offers = TradeRecord.all()
        my_offers = all_offers.filter('to =', str(self.key()))
        return my_offers
        
        
class Log(db.Model):
    accountName = db.StringProperty()
    message = db.StringProperty()
    currentPlanet = db.StringProperty()
    cost = db.StringProperty()
    read = db.BooleanProperty(default = False)
    created_at = db.DateTimeProperty(auto_now_add=True)
    
    @staticmethod
    def log(account, message, planet, cost):
        l = Log(accountName = account, message = message, currentPlanet = planet, cost = cost)
        l.put()
        return l
    
    @staticmethod
    def recent_logs_for(account, numFetch):
        query = Log.all()
        query.order('created_at')
        query.filter('account', account)
        #query.filter('read = ', False)
        
        logs = []
        results = query.fetch(numFetch)
        for result in results:
            logs.append(result)
            result.read = True
            result.put()
        
        return logs

# This is actually a virtual model
# None of the information is stored in the database, since it
#  doesn't change
class Good:
    def __init__(self, name, number, account):
        self.name = name
        self.amount = number
        self.owner = account
    
    def icon(self):
        return Good.icon_for(self.name)
    
    # Return true if this good has enough for a level up
    #  based on its owner's current level
    def contains_enough_for_upgrade(self):
        level = self.owner.level + 1
        
        # For now, assume it's a common good
        if Level.goods(level)[0] <= self.amount:
            return True
        return False
    
    @staticmethod
    def all():
        return ['Mineral', 'Water', 'Fuel', 'Organic']
    
    @staticmethod
    def get_random_good():
        goods = Good.all()
        return goods[random.randrange(0, len(goods))]
    
    # Return a string pointing to the icon for the given good
    @staticmethod
    def icon_for(name):
        options = {'crops':'c1', 'lumber':'c3', 'minerals':'c5', 'ore':'c7'}
        try:
            return "tiles_r1_"+options[name]+".png"
        except:
            return ""

# This is a virtual model with nothing in the database
class Level:
    
    # Returns the XP required for the given level
    @staticmethod
    def xp(level_num):
        # Exponential: 5, 20, 45, 80, etc.
        return 5*(level_num*level_num)
    
    # Returns the goods requirement for the given level in the form
    #  [# of goods, # of common types, # of goods, # of specialty types]
    #  ex: [10, 2, 5, 1] means 10 each of 2 common goods and 5 of 1
    #  specialty good
    @staticmethod
    def goods(level_num):
        return [10, 1, 0, 0]

# The warehouse contains goods for an account
class Warehouse(db.Model):
    owner = db.ReferenceProperty(Account)
    good = db.StringProperty()
    amount = db.IntegerProperty()
    
    @staticmethod
    def create(owner, good, amount):
        w = Warehouse(owner = owner, good = good, amount = amount)
        w.put()
        return w
    
    # Return true if this warehouse contains enough for a level up
    #  based on its owner's current level
    def contains_enough_for_upgrade(self):
        level = self.owner.level + 1
        
        # For now, assume it's a common good
        if Level.goods(level)[0] <= self.amount:
            return True
        return False

# A ship type available for sale
class UnitType(db.Model):
    type = db.StringProperty(required = True)
    price = db.ReferenceProperty(ResourcePackage)
    population = db.IntegerProperty() #add population to ships
    description = db.StringProperty(required = True)

    @staticmethod
    def create(t, p, pp, d):
        st = UnitType(type = t, price = p, population = pp, description = d)
        st.put()
        return st

class BuildingType(db.Model):
    name = db.StringProperty()
    type = db.StringProperty(required = True)
    price = db.ReferenceProperty(ResourcePackage)
    population = db.IntegerProperty()
    description = db.StringProperty(required = True)

    @staticmethod
    def create(n, t, p, pp, d):
        bt = BuildingType(name = n, type = t, price = p, population = pp, description = d)
        bt.put()
        return bt

class BuildingUnit(db.Model):
    owner = db.ReferenceProperty(Account)
    planet = db.ReferenceProperty(Planet)
    type = db.ReferenceProperty(BuildingType)

class OutPost(db.Model):
    owner = db.ReferenceProperty(Account)
    planet = db.ReferenceProperty(Planet)
    resourceStorage = db.ReferenceProperty(ResourcePackage)
    @staticmethod
    def create(o, p, r):
        op = OutPost(owner = o, planet = p, resourceStorage = r)
        op.put()
        return op

class RecyclingShip(db.Model):
    owner = db.ReferenceProperty(Account)
    name = db.StringProperty()
    planet = db.ReferenceProperty(Planet)
    type = db.ReferenceProperty(UnitType)
    fuel = db.IntegerProperty(default = 2000)

class ColonyShip(db.Model):
    owner = db.ReferenceProperty(Account)
    name = db.StringProperty()
    planet = db.ReferenceProperty(Planet)
    type = db.ReferenceProperty(UnitType)
    fuel = db.IntegerProperty(default = 2000)

class FreighterShip(db.Model):
    owner = db.ReferenceProperty(Account)
    name = db.StringProperty()
    planet = db.ReferenceProperty(Planet)
    type = db.ReferenceProperty(UnitType)
    # for use by the freighter ship
    storage = db.ReferenceProperty(ResourcePackage)
    max_storage = db.IntegerProperty(default = 1000)
    fuel = db.IntegerProperty(default = 2000)

class MOPPShip(db.Model):
    owner = db.ReferenceProperty(Account)
    name = db.StringProperty()
    planet = db.ReferenceProperty(Planet)
    type = db.ReferenceProperty(UnitType)
    fuel = db.IntegerProperty(default = 2000)

class MegaFreighterShip(db.Model):
    owner = db.ReferenceProperty(Account)
    name = db.StringProperty()
    planet = db.ReferenceProperty(Planet)
    type = db.ReferenceProperty(UnitType)
    # for use by the freighter ship
    storage = db.ReferenceProperty(ResourcePackage)
    max_storage = db.IntegerProperty(default = 10000)
    fuel = db.IntegerProperty(default = 2000)
        
# Defines a unit owned by a player
class Unit(db.Model):
    owner = db.ReferenceProperty(Account)
    # What planet, if any, is it defending?
    planet = db.ReferenceProperty(Planet)
    type = db.ReferenceProperty(UnitType)
    x_pos = db.IntegerProperty(default = None)
    y_pos = db.IntegerProperty(default = None)
    
    # Return a random valid attack value
    def attack(self):        
        # Roll the die to get a value from min attack to max attack
        # Adds one because the last value is exclusive
        return random.randrange(type.min_attack, type.max_attack+1)
    
    # Is this unit available (e.g. not defending)
    def is_available(self):
        # If they're defending, they're unavailable
        if self.is_defending():
            return False
            
        return True
    
    # Is this unit defending?
    def is_defending(self):
        return self.planet != None

# Defines an offer of a particular good by a particular person
class Offer(db.Model):
    owner = db.ReferenceProperty(Account)
    created_at = db.DateTimeProperty(auto_now_add=True)
    offer_good = db.StringProperty()
    offer_amount = db.IntegerProperty()
    request_good = db.StringProperty()
    request_amount = db.IntegerProperty()
    location = db.ReferenceProperty(Planet)
    
    # Returns an array (of one) of the good being offered
    def offer_goods(self):
        good = Good(self.offer_good, self.offer_amount, self.owner)
        return [good]
    
    # Returns an array (of one) of the good being requested
    def request_goods(self):
        good = Good(self.request_good, self.request_amount, self.owner)
        return [good]
    
    # Returns a range consisting of groups of 10 based on the number of sets
    # e.g. 2 sets returns [10, 20]
    def sets(self):
        return range(10,(int(self.offer_amount/10)+1)*10, 10)
    @staticmethod
    def update_good(colony, resStr, amnt):
        goods = Good.all()
        thisResPkg = colony.resourceStorage
        if ( resStr == goods[0] ):
            thisResPkg.resourceOne += amnt
        elif ( resStr == goods[1] ):
            thisResPkg.resourceTwo += amnt
        elif ( resStr == goods[2] ):
            thisResPkg.resourceThree += amnt
        elif ( resStr == goods[3] ):
            thisResPkg.resourceFour += amnt

        thisResPkg.put()
    
    """
    # Returns the first 30 offers that match a given query, sorted
    #  by ascending price (None to avoid sorting on that field)
    @staticmethod
    def filter(universe, offered, requested):
        query = Offer.all().filter('universe = ', universe)
        if (offered != None and offered != ''):
            query = query.filter('offer_good = ', offered)
        if (requested != None and requested != ''):
            query = query.filter('request_good = ', requested)
        
        return query.order('request_amount').fetch(limit = 30)

    """
        
        
# This is actually a virtual model
# None of the information is stored in the database, since it
#  doesn't change
class SolarSystem:
    # Return a random universe (right now, hard-coded to 'A' for testing)
    # This means that everyone starts in the same universe
    @staticmethod
    def random():
        solar_systems = ['Sunna']
        return solar_systems[random.randrange(0, len(solar_systems))]

# This is new
class DestinationRecord(db.Model):
    destination = db.ReferenceProperty(Planet)
    cost = db.IntegerProperty()
    ship_type = db.StringProperty()
    # this has the key to the generic ship object
    ship = db.StringProperty()
    
    
class Colony(db.Model):
    owner = db.ReferenceProperty(Account)
    location = db.ReferenceProperty(Planet)
    resourceStorage = db.ReferenceProperty(ResourcePackage)
    collectable = db.BooleanProperty(default=True)
    
    def good_amount_for(self, resName):
        resList = Good.all()
        
        if (resList[0] == resName):
            return self.resourceStorage.resourceOne
        elif (resList[1] == resName):
            return self.resourceStorage.resourceTwo
        elif (resList[2] == resName):
            return self.resourceStorage.resourceThree
        elif (resList[3] == resName):
            return self.resourceStorage.resourceFour
        else:
            return -1

    @staticmethod
    def get_colony(account, planet):
        all_colonies = Colony.all()
        my_colonies = all_colonies.filter('owner =', account).filter('location =', planet)
        my_colony = my_colonies[0]
        
        return my_colony
        
class ProfilerRecord(db.Model):
    planet = db.ReferenceProperty(Planet)
    colony = db.ReferenceProperty(Colony)
    distance = db.IntegerProperty()
    
    
class TradeRecord(db.Model):
    owner = db.ReferenceProperty(Account)
    to = db.StringProperty()
    location = db.ReferenceProperty(Planet)
    
    give_resOne = db.IntegerProperty(default=0)
    give_resTwo = db.IntegerProperty(default=0)
    give_resThree = db.IntegerProperty(default=0)
    give_resFour = db.IntegerProperty(default=0)
    give_colonyShips = db.IntegerProperty(default=0)
    give_recyclerShips = db.IntegerProperty(default=0)
    give_freighterShips = db.IntegerProperty(default=0)
    
    take_resOne = db.IntegerProperty(default=0)
    take_resTwo = db.IntegerProperty(default=0)
    take_resThree = db.IntegerProperty(default=0)
    take_resFour = db.IntegerProperty(default=0)
    take_colonyShips = db.IntegerProperty(default=0)
    take_recyclerShips = db.IntegerProperty(default=0)
    take_freighterShips = db.IntegerProperty(default=0)
    
    message = db.TextProperty()
    created_at = db.DateTimeProperty(auto_now_add=True)    
 

 
class MessageRecord(db.Model):
    owner = db.ReferenceProperty(Account)
    to = db.StringProperty()
    content = db.TextProperty()
    created_at = db.DateTimeProperty(auto_now_add=True)    
    
